/*===========================================================================*\
|
|  File:        cgmidi.cpp
|
|  Description: 
|       
|-----------------------------------------------------------------------------
|
|  Copyright (C) 1995-1996 Microsoft Corporation.  All Rights Reserved.
|
|  Written by Moss Bay Engineering, Inc. under contract to Microsoft Corporation
|
\*===========================================================================*/
/**************************************************************************

    (C) Copyright 1995-1996 Microsoft Corp.  All rights reserved.

    You have a royalty-free right to use, modify, reproduce and 
    distribute the Sample Files (and/or any modified version) in 
    any way you find useful, provided that you agree that 
    Microsoft has no warranty obligations or liability for any 
    Sample Application Files which are modified. 

    we do not recomend you base your game on IKlowns, start with one of
    the other simpler sample apps in the GDK

 **************************************************************************/

#include <windows.h>
#include <windowsx.h>
#include <mmsystem.h>
#include "cgmidi.h"
#include "MDSPlay.H"


#define FOURCC_MIDS mmioFOURCC('M','I','D','S')
#define FOURCC_fmt  mmioFOURCC('f','m','t',' ')
#define FOURCC_data mmioFOURCC('d','a','t','a')

// Format of structs within a MDS file
//
// 'fmt ' chunk
//
#define MDS_F_NOSTREAMID    0x00000001
typedef struct
{
    DWORD           dwTimeFormat;       // Low word == time format in SMF format
    DWORD           cbMaxBuffer;        // Guaranteed max buffer size
    DWORD           dwFlags;
} MIDSFMT;

// 'data' chunk buffer header
//
typedef struct
{
    DWORD           tkStart;            // Absolute tick offset at start of buffer
    DWORD           cbBuffer;           // Bytes in this buffer
} MIDSBUFFER;

// An image handle points to this
//

#define MDSI_F_RESET        0x00000001L
#define MDSI_F_LOOP         0x00000002L
#define MDSI_F_PAUSED       0x00000004L

#define FOURCC_MDSI         mmioFOURCC('M','D','S','I')

#define V_HIMAGE(x) \
    if (((MDSIMAGE*)x)->fccSig != FOURCC_MDSI) \
        return MDS_ERR_INVALHANDLE;

typedef struct
{
    FOURCC          fccSig;             // MDS image handle signature
    MIDSFMT         fmt;                // MDS file format header
    PBYTE           pbBufferAlloc;      // All MIDIHDR's/buffers
    HMIDISTRM       hms;                // MIDI stream if open
    DWORD           fdwImage;           // Generic flags
    DWORD           cBuffers;           // Total buffers
    DWORD           cBuffersInUse;      // Buffers MMSYSTEM owns right now
} MDSIMAGE;

DWORD   ParseImage(MDSIMAGE* pImage, PBYTE pbImage, DWORD cbImage);
BOOL    Decompress(LPMIDIHDR lpmhSrc, LPMIDIHDR lpmhDst);

void FAR PASCAL midiCallback(HMIDISTRM hms, UINT uMsg, DWORD dwUser, DWORD dw1, DWORD dw2);

#define MDS_F_IMAGEFLAGS    (MDS_F_MEMORY|MDS_F_FILENAME)

// LoadMDSImage
//
// Allocate space for the handle structure MDSImage and get the 
// image into memory if it's in a file. Call ParseImage to
// allocate buffers and parse the image into them.
//
DWORD LoadMDSImage(HANDLE *hImage, PBYTE pbImage, DWORD cbImage, DWORD fdw)
{
    DWORD                       dwRet       = MDS_SUCCESS;
    MDSIMAGE*                   pImage      = NULL;
    BOOL                        fIsMapped   = FALSE;
    HANDLE                      hInFile     = INVALID_HANDLE_VALUE;
    HANDLE                      hInFileMap  = NULL;

    // Must have one of the two image flags
    //
    if ((!(fdw & MDS_F_IMAGEFLAGS)) ||
        ((fdw & MDS_F_IMAGEFLAGS) == MDS_F_IMAGEFLAGS))
    {
        dwRet = MDS_ERR_BADFLAGS;
        goto Load_Cleanup;
    }
    
    // Allocate the handle
    //          
    pImage = (MDSIMAGE*)LocalAlloc(LPTR, sizeof(MDSIMAGE));
    if (!pImage)
    {
        dwRet = MDS_ERR_NOMEM;
        goto Load_Cleanup;
    }

    pImage->fccSig          = FOURCC_MDSI;
    pImage->hms             = NULL;
    pImage->cBuffersInUse   = 0;



    // Read the image if we need to 
    //
    if (!(fdw & MDS_F_MEMORY))
    {
        fIsMapped = TRUE;

        // Try to map the file as an image
        //
        hInFile = CreateFile(
            (LPSTR)pbImage, GENERIC_READ, FILE_SHARE_READ, NULL,
            OPEN_EXISTING, 
            FILE_ATTRIBUTE_NORMAL, NULL);

        pbImage = NULL;
        if (INVALID_HANDLE_VALUE == hInFile)
        {
            dwRet = MDS_ERR_NOFILE;
            goto Load_Cleanup;
        }

        cbImage = GetFileSize(hInFile, NULL);

        hInFileMap = CreateFileMapping(
            hInFile, NULL, PAGE_READONLY, 0, 0, NULL);
        if (NULL == hInFileMap)
        {
            dwRet = MDS_ERR_NOFILE;
            goto Load_Cleanup;
        }

        pbImage = (PBYTE)MapViewOfFile(
            hInFileMap, FILE_MAP_READ, 0, 0, 0);
        if (NULL == pbImage)
        {
            dwRet = MDS_ERR_NOFILE;
            goto Load_Cleanup;
        }
    }

    // pbImage now points to the file image in memory. Attempt to parse it.
    //
    dwRet = ParseImage(pImage, pbImage, cbImage);

Load_Cleanup:
    if (dwRet)
    {
        if (pImage) LocalFree((HLOCAL)pImage);
    }
    else
    {
        *hImage = (HANDLE)pImage;
    }

    if (fIsMapped)
    {
        if (NULL != pbImage)                    UnmapViewOfFile(pbImage);
        if (NULL != hInFileMap)                 CloseHandle(hInFileMap);
        if (INVALID_HANDLE_VALUE != hInFile)    CloseHandle(hInFile);
    }

    return dwRet;
}

// Given the file image, allocate MIDI stream buffers and put the 
// image data into them. The file image will go away when LoadMDSImage
// returns, so this routine must save all important info somewhere off
// the handle structure pImage.
//
DWORD ParseImage(MDSIMAGE* pImage, PBYTE pbImage, DWORD cbImage)
{
    DWORD                       dwRet       = MDS_SUCCESS;
    DWORD                       cbChk;
    DWORD                       idx;
    MIDIHDR                     mhSrc;
    LPMIDIHDR                   lpmh;
    MIDSBUFFER                  mb;

    pImage->pbBufferAlloc = NULL;

    // Parse: RIFF + cbChk (size of rest of file) + MIDS
    //
    if ((cbImage < 2*sizeof(FOURCC) + sizeof(DWORD)) ||
        (FOURCC_RIFF != *(FOURCC*)pbImage) ||
        (FOURCC_MIDS != *(FOURCC*)(pbImage + sizeof(FOURCC) + sizeof(DWORD))))
    {
        dwRet = MDS_ERR_BADFILE;
        goto Parse_Cleanup;
    }

    // Note: can't subtract off size of 'fmt ' FOURCC until we check against
    // cbChk size... it's included in the RIFF chunk, not the header
    //
    cbChk = *(((PDWORD)pbImage)+1);
    pbImage += sizeof(FOURCC) + sizeof(DWORD);
    cbImage -= sizeof(FOURCC) + sizeof(DWORD);

    if (cbImage < cbChk)
    {
        dwRet = MDS_ERR_BADFILE;
        goto Parse_Cleanup;
    }

    pbImage += sizeof(FOURCC);
    cbImage -= sizeof(FOURCC);


    // Should have 'fmt ' chunk first
    //
    if ((cbImage < sizeof(FOURCC) + sizeof(DWORD)) ||
        (FOURCC_fmt != *(FOURCC*)pbImage) ||
        ((cbChk = *(PDWORD)(pbImage + sizeof(FOURCC))) > cbImage) ||
        cbChk < sizeof(pImage->fmt))
    {
        dwRet = MDS_ERR_BADFILE;
        goto Parse_Cleanup;
    }

    pbImage += sizeof(FOURCC) + sizeof(DWORD);
    cbImage -= sizeof(FOURCC) + sizeof(DWORD);

    // Already validated size, copy format chunk
    //
    pImage->fmt = *(MIDSFMT*)pbImage;
    
    pbImage += cbChk;
    cbImage -= cbChk;

    // Should get buffers next
    //
    if ((cbImage < sizeof(FOURCC) + sizeof(DWORD)) ||
        (FOURCC_data != *(FOURCC*)pbImage) ||
        ((cbChk = *(PDWORD)(pbImage + sizeof(FOURCC))) > cbImage) ||
        cbChk < sizeof(DWORD))
    {
        dwRet = MDS_ERR_BADFILE;
        goto Parse_Cleanup;
    }

    pImage->cBuffers = *(PDWORD)(pbImage + sizeof(FOURCC) + sizeof(DWORD));

    pbImage += sizeof(FOURCC) + 2*sizeof(DWORD);
    cbImage -= sizeof(FOURCC) + 2*sizeof(DWORD);

    // Now copy the data out, decompressing if needed
    //

    // Total decompressed size including MIDIHDR's to hold them
    //
    // Allocate as one big block and put them into a buffer list
    //
    cbChk = pImage->cBuffers * (sizeof(MIDIHDR) + pImage->fmt.cbMaxBuffer);
    pImage->pbBufferAlloc = (PBYTE)GlobalAllocPtr(
        GMEM_MOVEABLE|GMEM_SHARE, cbChk);
    if (NULL == pImage->pbBufferAlloc)
    {
        dwRet = MDS_ERR_NOMEM;
        goto Parse_Cleanup;
    }
    
    lpmh = (LPMIDIHDR)(pImage->pbBufferAlloc);
    for (idx = pImage->cBuffers; idx; --idx)
    {
        lpmh->lpData = (LPSTR)(lpmh + 1);
        lpmh->dwBufferLength = pImage->fmt.cbMaxBuffer;
        lpmh->dwFlags = 0;
        lpmh->dwUser = (DWORD)pImage;
        lpmh->lpNext = NULL;

        if (cbImage < sizeof(mb))
        {
            dwRet = MDS_ERR_BADFILE;
            goto Parse_Cleanup;
        }

        mb = *(MIDSBUFFER*)pbImage;
        cbImage -= sizeof(mb);
        pbImage += sizeof(mb);

        if (mb.cbBuffer > pImage->fmt.cbMaxBuffer ||
            mb.cbBuffer > cbImage)
        {
            dwRet = MDS_ERR_BADFILE;
            goto Parse_Cleanup;
        }

        if (!(pImage->fmt.dwFlags & MDS_F_NOSTREAMID))
        {
            lpmh->dwBytesRecorded = mb.cbBuffer;
            hmemcpy(lpmh->lpData, pbImage, mb.cbBuffer);
        }
        else
        {
            mhSrc.lpData = (LPSTR)pbImage;
            mhSrc.dwBufferLength = mhSrc.dwBytesRecorded = mb.cbBuffer;
            if (!Decompress(&mhSrc, lpmh))
            {
                dwRet = MDS_ERR_BADFILE;
                goto Parse_Cleanup;
            }
        }

        cbImage -= mb.cbBuffer;
        pbImage += mb.cbBuffer;

        lpmh = (LPMIDIHDR)(((PBYTE)lpmh) + sizeof(MIDIHDR) + pImage->fmt.cbMaxBuffer);
    }


Parse_Cleanup:

    if (dwRet)
    {
        if (pImage->pbBufferAlloc) {
        GlobalFreePtr(pImage->pbBufferAlloc);
        }
    }
    
    return dwRet;
}

// Compression is simply removing the DWORD of stream ID per event since it's
// 0 most of the time unless some really funky authoring is being done. This 
// will save 1 of 3 DWORD's on a MIDI short event, reducing the file size
// to 2/3 of what it was.
//
BOOL Decompress(LPMIDIHDR lpmhSrc, LPMIDIHDR lpmhDst)
{
    LPDWORD                 lpSrc       = (LPDWORD)lpmhSrc->lpData;
    LPDWORD                 lpDst       = (LPDWORD)lpmhDst->lpData;
    DWORD                   cbSrc       = lpmhSrc->dwBytesRecorded;
    DWORD                   cbDst       = lpmhDst->dwBufferLength;
    DWORD                   cbExtra;

    // Total buffer length must be a DWORD multiple
    //
    if (cbSrc & 3)
        return FALSE;

    // !!! OPTIMIZE THIS LOOP !!!
    //
    while (cbSrc)
    {   
        // Need at least space for delta-t, stream-id, event DWORD
        // 
        if (cbDst < 3 * sizeof(DWORD))
            return FALSE;

                                            
        // Event delta-time
        //
        *lpDst++ = *lpSrc++;                
        cbSrc -= sizeof(DWORD);

        // Any event left?
        //
        if (!cbSrc)
            return FALSE;

        // Stream ID
        *lpDst++ = 0;
        cbDst -= 2*sizeof(DWORD);

        // Now copy the actual event data
        //
        cbExtra = 0;
        if ((*lpSrc) & 0x80000000L)
            cbExtra = (*lpSrc) & 0x00FFFFFFL;

        // Long event length is byte aligned, but data is padded to next DWORD
        // in file. 
        //
        cbExtra = (cbExtra + 3) & ~3;

        // Event DWORD itself
        //
        *lpDst++ = *lpSrc++;
        cbSrc -= sizeof(DWORD);
        cbDst -= sizeof(DWORD);

        // Long event parameter data
        //
        if (cbExtra)
        {
            if (cbExtra > cbSrc || cbExtra > cbDst)
                return FALSE;
            
            hmemcpy(lpDst, lpSrc, cbExtra);
        }

//      assert(0 == (cbExtra % sizeof(DWORD)));

        lpDst += (cbExtra / sizeof(DWORD));
        lpSrc += (cbExtra / sizeof(DWORD));
        cbSrc -= cbExtra;
        cbDst -= cbExtra;
    }

    lpmhDst->dwBytesRecorded = (((LPBYTE)lpDst) - (LPBYTE)(lpmhDst->lpData));

    return TRUE;
}

// FreeMDSImage
//
// Get rid of all resources associated with this handle. Stops playback
// if running.
//
DWORD FreeMDSImage(HANDLE hImage)
{
    MDSIMAGE*                   pImage;

    V_HIMAGE(hImage);
    pImage = (MDSIMAGE*)hImage;
    
    if (NULL != pImage->hms)
        StopMDS(hImage);

    if (pImage->pbBufferAlloc)  GlobalFreePtr(pImage->pbBufferAlloc);

    // Toast signature in case free'ing the block doesn't; this will
    // cause future V_HIMAGE's to faile
    //
    pImage->fccSig = FOURCC_data;

    LocalFree((HLOCAL)pImage);

    return MDS_SUCCESS;
}

// PlayMDS
//
// Start playback.
//  Open the device if needed.
//  Send the ready list
//
DWORD PlayMDS(HANDLE hImage, DWORD fdw)
{
    DWORD                       dwRet   = MDS_SUCCESS;
    MDSIMAGE*                   pImage;
    LPMIDIHDR                   lpmh;
    UINT                        uDeviceID;
    BOOL                        fCloseOnFail = FALSE;
    DWORD                       idx;
    MIDIPROPTIMEDIV             mptd;

    V_HIMAGE(hImage);

    pImage = (MDSIMAGE*)hImage;
    
    if (pImage->hms && !(pImage->fdwImage & MDSI_F_PAUSED))
        return MDS_ERR_BADSTATE;

    if (!(pImage->hms))
    {
        fCloseOnFail = TRUE;

        // Starting from scratch. Try to open the MIDI device
        //
        uDeviceID = MIDI_MAPPER;
        if (MMSYSERR_NOERROR != midiStreamOpen(
            &pImage->hms, 
            &uDeviceID, 
            1, 
            (DWORD)midiCallback, 
            0L, 
            CALLBACK_FUNCTION))
        {
            dwRet = MDS_ERR_MIDIERROR;
            goto Play_Cleanup;
        }

        mptd.cbStruct = sizeof(mptd);
        mptd.dwTimeDiv = pImage->fmt.dwTimeFormat;

        if (MMSYSERR_NOERROR !=midiStreamProperty(
            pImage->hms, (LPBYTE)&mptd, MIDIPROP_SET|MIDIPROP_TIMEDIV))
        {
            dwRet = MDS_ERR_MIDIERROR;
            goto Play_Cleanup;
        }

        // Headers are put back into the ready queue by a midiOutReset on
        // stop, but are not guaranteed to be in correct order. Resend 
        // directly from the allocated chunk-of-all-buffers
        //

//      assert(0 == pImage->cBuffersInUse);

        lpmh = (LPMIDIHDR)(pImage->pbBufferAlloc);
        for (idx = pImage->cBuffers; idx; --idx)
        {
            if (MMSYSERR_NOERROR != midiOutPrepareHeader(
                                (HMIDIOUT)pImage->hms, lpmh, sizeof(*lpmh)) ||
                MMSYSERR_NOERROR != midiStreamOut(
                pImage->hms, lpmh, sizeof(*lpmh)))
            {
                dwRet = MDS_ERR_MIDIERROR;
                goto Play_Cleanup;
            }

            ++pImage->cBuffersInUse;

            lpmh = (LPMIDIHDR)(((PBYTE)lpmh) + sizeof(MIDIHDR) + lpmh->dwBufferLength);
        }       
    }

    // Whether we're starting or resuming from paused, just need
    // to restart

    pImage->fdwImage &= ~MDSI_F_LOOP;
    if (fdw & MDS_F_LOOP)
        pImage->fdwImage |= MDSI_F_LOOP;

    pImage->fdwImage &= ~MDSI_F_PAUSED;
    if (MMSYSERR_NOERROR != midiStreamRestart(
        pImage->hms))
    {
        dwRet = MDS_ERR_MIDIERROR;
        goto Play_Cleanup;
    }

Play_Cleanup:

    if (dwRet)
    {
        if (fCloseOnFail && pImage->hms)
            StopMDS(hImage);
    }
    
    return dwRet;
}

// PauseMDS
//
// Pause the stream if it's playing
//
DWORD PauseMDS(HANDLE hImage)
{
    MDSIMAGE*                   pImage;

    V_HIMAGE(hImage);

    pImage = (MDSIMAGE*)hImage;

    if (NULL == pImage->hms)
        return MDS_ERR_BADSTATE;
    
    if (pImage->fdwImage & MDSI_F_PAUSED)
        return MDS_SUCCESS;

    if (MMSYSERR_NOERROR != midiStreamPause(
        pImage->hms))
        return MDS_ERR_MIDIERROR;

    pImage->fdwImage |= MDSI_F_PAUSED;

    return MDS_SUCCESS;
}

// StopMDS
//
// Stop the stream, reset to the start, close the device
// Do NOT free stream tho
//
DWORD StopMDS(HANDLE hImage)
{
    MDSIMAGE*                   pImage;
    DWORD                       idx;
    LPMIDIHDR                   lpmh;

    V_HIMAGE(hImage);

    pImage = (MDSIMAGE*)hImage;
        
    if (NULL == pImage->hms)
        return MDS_ERR_BADSTATE;

    // Remove all the buffers from MMSYSTEM
    //
    pImage->fdwImage |= MDSI_F_RESET;
    if (MMSYSERR_NOERROR != midiOutReset(
                (HMIDIOUT)pImage->hms))
    {
        pImage->fdwImage &= ~MDSI_F_RESET;
        return MDS_ERR_MIDIERROR;
    }

//  assert(0 == pImage->cBuffersInUse);

    // Unprepare everyone
    //
    lpmh = (LPMIDIHDR)(pImage->pbBufferAlloc);
    for (idx = pImage->cBuffers; idx; --idx)
    {
                midiOutUnprepareHeader((HMIDIOUT)pImage->hms, lpmh, sizeof(*lpmh));
        lpmh = (LPMIDIHDR)(((PBYTE)lpmh) + sizeof(MIDIHDR) + lpmh->dwBufferLength);
    }       
    
    midiStreamClose(pImage->hms);
    pImage->hms = NULL;
    pImage->fdwImage = 0;

    return MDS_SUCCESS;
}


// Callback
//
// Keep things rolling or collect the buffers back in the queue
//
void FAR PASCAL midiCallback(HMIDISTRM hms, UINT uMsg, DWORD dwUser, DWORD dw1, DWORD dw2)
{
    MDSIMAGE*                   pImage;
    LPMIDIHDR                   lpmh        = (LPMIDIHDR)dw1;

    if (uMsg != MOM_DONE)
        return;

//  assert(NULL != lpmh);
    pImage = (MDSIMAGE*)lpmh->dwUser;
//  assert(FOURCC_MDSI == pImage->fccSig);

    pImage = (MDSIMAGE*)lpmh->dwUser;

    if ((pImage->fdwImage & MDSI_F_LOOP) && !(pImage->fdwImage & MDSI_F_RESET))
        if (MMSYSERR_NOERROR == midiStreamOut(
            pImage->hms, lpmh, sizeof(*lpmh)))
            return;
            
    --pImage->cBuffersInUse;
}












typedef struct _MIDI_INFO
{
    HANDLE  hImage;
    BOOL    fPlaying;
} MIDI_INFO;

static MIDI_INFO    *pMidi = NULL;

/* Plays a given MIDI file using MCI_OPEN, MCI_PLAY. Returns as soon as
 * playback begins. The window procedure function for the given window
 * will be notified when playback is complete.
 */
BOOL playMusic(LPSTR lpszMIDIFileName, BOOL fAutoStart)
{
    if (pMidi != NULL) {
    closeMusic();
    }

    pMidi = new MIDI_INFO;

    
    if( LoadMDSImage( &(pMidi->hImage),
              (PBYTE)lpszMIDIFileName,
              0,
              MDS_F_FILENAME ) != 0 ) {
    delete pMidi;
    pMidi = NULL;
    return (FALSE);
    }

    pMidi->fPlaying = FALSE;
    if( fAutoStart ) {
    if( PlayMDS(pMidi->hImage, MDS_F_LOOP) != 0 ) {
        FreeMDSImage(pMidi->hImage);
        delete pMidi;
        pMidi = NULL;
        return (FALSE);
    }
    pMidi->fPlaying = TRUE;

    }

    
    return (TRUE);
}


void resumeMusic()
{
    if ( pMidi == NULL  ) {
    // Error
    return;
    }

    if( PlayMDS(pMidi->hImage, MDS_F_LOOP) != 0 ) {
    // Error
    return;
    }   
    pMidi->fPlaying = TRUE;

}   

void pauseMusic()
{
    if ( pMidi == NULL  ) {
    // Error
    return;
    }

    if( PauseMDS(pMidi->hImage) != 0 ) {
    // Error
    return;
    }   
    pMidi->fPlaying = FALSE;

}   

void restartMusic()
{
    if ( pMidi == NULL  ) {
    // Error
    return;
    }


    if( StopMDS(pMidi->hImage) != 0 ) {
    // Error
    return;
    }   
    if( PlayMDS(pMidi->hImage, MDS_F_LOOP) != 0 ) {
    // Error
    return;
    }   
    pMidi->fPlaying = TRUE;

}

void closeMusic()
{
    if ( pMidi == NULL  ) {
    // Error
    return;
    }

    StopMDS(pMidi->hImage);
    FreeMDSImage(pMidi->hImage);
    
    delete pMidi;
    pMidi = NULL;

        
}   
